package com.fengwk.user.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.fengwk.support.exception.BaseException;
import com.fengwk.support.redis.RedisDatabase;
import com.fengwk.support.util.IoUtils;
import com.fengwk.support.util.ObjectUtils;
import com.fengwk.support.util.StringUtils;
import com.fengwk.user.face.entity.UsChannel;
import com.fengwk.user.face.entity.UsToken;
import com.fengwk.user.face.model.LoginSession;
import com.fengwk.user.face.model.Token;
import com.fengwk.user.face.model.User;
import com.fengwk.user.service.ChannelService;
import com.fengwk.user.service.EncodeService;
import com.fengwk.user.service.LoginService;
import com.fengwk.user.service.RandomService;
import com.fengwk.user.service.TokenService;
import com.fengwk.user.service.UserService;

@Service
public class LoginServiceImpl implements LoginService {
	
	private static final String REDIS_KEY_SPLIT = "_";
	
	@Autowired
	private RedisDatabase redisDatabase;
	
	@Autowired
	private UserService userService;
	@Autowired
	private TokenService tokenService;
	@Autowired
	private EncodeService encodeService;
	@Autowired
	private RandomService randomService;
	@Autowired
	private ChannelService channelService;
	
	@Override
	public LoginSession getLoginSessionByChannelAndToken(String channel, String token) {
		LoginSession loginSession = null;
		if (StringUtils.isNotBlank(channel) && StringUtils.isNotBlank(token)) {
			byte[] loginSessionBytes = redisDatabase.get(encodeRedisKey(channel, token));
			if (loginSessionBytes != null) {
				loginSession = (LoginSession) IoUtils.bytesToObject(loginSessionBytes);
			}
		}
		return loginSession;
	}

	@Override
	public LoginSession login(String channel, String identity, String certification) {
		User user = userService.getUserByUsername(identity);
		if (!checkUsUser(user, identity, certification)) {
			user = userService.getUserByMobilePhone(identity);
			if (!checkUsUser(user, identity, certification)) {
				user = userService.getUserByEmail(identity);
				if (!checkUsUser(user, identity, certification))
					throw new BaseException("身份凭证校验失败");
			}
		}
		int strategy = channelService.getStrategyByChannel(channel);
		LoginSession loginSession = null;
		// 从缓存库中获取Token
		Token token = null;
		byte[] tokenBytes = redisDatabase.get(encodeRedisKey(channel, user.getUserId().toString()));
		if (tokenBytes != null) {
			token = (Token) IoUtils.bytesToObject(tokenBytes);
		}
		if (token != null) {
			if (ObjectUtils.equals(strategy, UsChannel.STRATEGY_SHARE)) {
				// 共享模式
				// 获取已存在的LoginSession并做更新
				loginSession = getLoginSessionByChannelAndToken(token.getChannel(), token.getToken());
				Token newToken = tokenService.reLoginToken(channel, token.getToken());
				loginSession.setUser(user);
				loginSession.setToken(newToken);
			} else if (ObjectUtils.equals(strategy, UsChannel.STRATEGY_EXCLUSIVE)) 
				// 排他模式
				// 先登出
				logout(channel, token.getToken());
		}
		
		if (loginSession == null) 
			// 当获取LoginSession失败时重新创建一个新的
			loginSession = createLoginSession(channel, user);
		
		// 存储当前的LoginSession
		saveLoginSession(loginSession);
		return loginSession;
	}
	
	private LoginSession createLoginSession(String channel, User user) {
		// 使用无超时TIMEOUT_INVALID
		Token token = tokenService.createToken(channel, user.getUserId(), UsToken.TIMEOUT_INVALID);
		LoginSession loginSession = new LoginSession();
		loginSession.setUser(user);
		loginSession.setToken(token);
		return loginSession;
	}
	
	private void saveLoginSession(LoginSession loginSession) {
		User user = loginSession.getUser();
		Long userId = user.getUserId();
		Token tokenE = loginSession.getToken();
		String channel = tokenE.getChannel();
		String token = tokenE.getToken();
		int timeout = tokenE.getTimeout();
		// channel_userId => Token
		redisDatabase.set(
				encodeRedisKey(channel, userId.toString()), 
				IoUtils.objectToBytes(tokenE), 
				timeout);
		// channel_token => LoginSession
		redisDatabase.set(
				encodeRedisKey(channel, token), 
				IoUtils.objectToBytes(loginSession), 
				timeout);
	}
	
	private boolean checkUsUser(User user, String identity, String certification) {
		if (user != null) {
			String username = user.getUsername();
			String mobilePhone = user.getMobilePhone();
			String email = user.getEmail();
			String password = user.getPassword();
			if ((ObjectUtils.equals(identity, username) 
					|| ObjectUtils.equals(identity, mobilePhone) 
					|| ObjectUtils.equals(identity, email)) 
					&& ObjectUtils.equals(encodeService.encodePassword(certification), password)) {
				return true;
			} else if (randomService.checkRandom(identity, certification)) {
				return true;
			}
		}
		return false;
	}

	@Override
	public void logout(String channel, String token) {
		LoginSession loginSession = getLoginSessionByChannelAndToken(channel, token);
		if (loginSession != null) {
			User user = loginSession.getUser();
			redisDatabase.del(encodeRedisKey(channel, user.getUserId().toString()));
			redisDatabase.del(encodeRedisKey(channel, token));
		}
	}

	private byte[] encodeRedisKey(String key1, String key2) {
		return (key1 + REDIS_KEY_SPLIT + key2).getBytes();
	}
	
}
